diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 22:31:23 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 22:31:23 +0200 |
| commit | 992e40835af1600a0961ec2454fb78ff43aec3c6 (patch) | |
| tree | 3c0602ae64a186ce5ac51be6b0795037488f552a /src/routes/projects/[id] | |
| parent | e4a158965f3fcfe6fac65a1eba7b456d615e7dca (diff) | |
Add routine archives: archive date, searchable archived list, frozen page
- Stamp archived_at on the archived snapshot; copy it on edit
- loadArchived(projectId) + collapsible fuzzy-searched "Archivées" section on the project page
- Archived routine pages open read-only with the health chart ending at the archive date; edit/complete actions reject archived routines
Diffstat (limited to 'src/routes/projects/[id]')
| -rw-r--r-- | src/routes/projects/[id]/+page.server.ts | 3 | ||||
| -rw-r--r-- | src/routes/projects/[id]/+page.svelte | 118 |
2 files changed, 121 insertions, 0 deletions
diff --git a/src/routes/projects/[id]/+page.server.ts b/src/routes/projects/[id]/+page.server.ts index e3c4edf..a23d834 100644 --- a/src/routes/projects/[id]/+page.server.ts +++ b/src/routes/projects/[id]/+page.server.ts @@ -6,6 +6,7 @@ import { archive, complete, loadProjectDashboard, + loadArchived, } from '$lib/server/services/routines'; import { numericId, parseFormString } from '$lib/server/services/validation'; import { parseIntervalToDays } from '$lib/shared/routine-metrics'; @@ -27,10 +28,12 @@ export const load: PageServerLoad = async ({ params }) => { throw error(404, 'Projet introuvable'); } const dashboard = await loadProjectDashboard(id, Date.now()); + const archived = await loadArchived(id); return { project: { id: project.id, name: project.name }, routines: dashboard.routines, healthScore: dashboard.healthScore, + archived, }; }; diff --git a/src/routes/projects/[id]/+page.svelte b/src/routes/projects/[id]/+page.svelte index c69d066..3d1aafe 100644 --- a/src/routes/projects/[id]/+page.svelte +++ b/src/routes/projects/[id]/+page.svelte @@ -1,5 +1,6 @@ <script lang="ts"> import { enhance } from '$app/forms'; + import { matchItems } from '$lib/components/fuzzy-search'; import { formatFrequency, parseIntervalToDays, @@ -18,6 +19,15 @@ const frequencyDays = $derived(parseIntervalToDays(frequency)); const editFrequencyDays = $derived(parseIntervalToDays(editFrequency)); + let showArchived = $state(false); + let archiveFilter = $state(''); + + const filteredArchived = $derived( + archiveFilter.trim() === '' + ? data.archived + : matchItems(data.archived, archiveFilter).map((m) => m.obj), + ); + const dateFormatter = new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'short', @@ -203,6 +213,48 @@ <p class="empty">Aucune routine</p> {/if} + {#if data.archived.length > 0} + <section class="archived-section"> + <button + class="archived-toggle" + type="button" + onclick={() => (showArchived = !showArchived)} + aria-expanded={showArchived} + > + <span class="chevron">{showArchived ? '▾' : '▸'}</span> + Archivées + <span class="count">{data.archived.length}</span> + </button> + {#if showArchived} + <input + class="form-input archived-search" + type="search" + bind:value={archiveFilter} + placeholder="Rechercher une routine archivée..." + autocomplete="off" + aria-label="Rechercher parmi les routines archivées" + /> + <ul class="item-list"> + {#each filteredArchived as routine (routine.id)} + <li class="item-row item-row--card archived-row"> + <a + class="routine-name" + href="/projects/{data.project.id}/routine/{routine.id}" + >{routine.name}</a + > + <span class="archived-date"> + archivée le {dateFormatter.format(new Date(routine.archivedAt))} + </span> + </li> + {/each} + </ul> + {#if filteredArchived.length === 0} + <p class="empty archived-empty">Aucune correspondance.</p> + {/if} + {/if} + </section> + {/if} + <footer class="page-footer"> <form method="POST" action="?/archiveProject" use:enhance> <button class="btn-archive" type="submit">Archiver le projet</button> @@ -308,6 +360,72 @@ color: var(--danger); } + .archived-section { + margin-top: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + .archived-toggle { + align-self: flex-start; + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.75rem; + border: none; + border-radius: 0.625rem; + background: transparent; + color: var(--text-secondary); + font-family: inherit; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + } + + .archived-toggle:hover { + background: var(--surface); + color: var(--text); + } + + .archived-toggle .chevron { + font-size: 0.75rem; + } + + .archived-toggle .count { + min-width: 1.25rem; + padding: 0 0.375rem; + border-radius: 9999px; + background: var(--surface); + text-align: center; + font-size: 0.75rem; + font-weight: 600; + } + + .archived-search { + width: 100%; + } + + .archived-row .routine-name { + flex-grow: 1; + color: var(--text-secondary); + } + + .archived-row .routine-name:hover { + color: var(--accent); + } + + .archived-date { + flex-shrink: 0; + font-size: 0.75rem; + color: var(--text-secondary); + } + + .archived-empty { + padding: 1rem 0; + } + .empty { margin-top: 1rem; padding: 2rem; |
